/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.transas.zorlog.data.log;

import java.nio.ByteBuffer;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.Map;

/**
 * @author amalikov
 *
 * Field is a named rule of getting value from raw bytes of packet as Double.
 * Usage:
 *   1. Create fields using fabric method create.
 *   2. Put them in List.
 *   3. For all Field object in List use method getValue passing packet as ByteBuffer.
 *
 */
public abstract class Field {
	static final boolean SIGNED = true;
	static final boolean UNSIGNED = false;

	/**
	 * Returns value of the field as Double.
	 * @param buf - packet as ByteBuffer
	 */
	public abstract Double getValue(ByteBuffer buf);

	/**
	 * Returns value of the field as String.
	 * @param buf - packet as ByteBuffer
	 */
	public String getValueAsString(ByteBuffer buf, NumberFormat nf) {
		nf.setMaximumFractionDigits(precision);
		nf.setMinimumFractionDigits(precision);
		return nf.format(getValue(buf));
	}

	public final String getAttribute(String name) {
		return attr.get(name);
	}
	
	/**
	 * Creates field by type.
	 * @param offset - field offset in packet.
	 * @param type - type of field, supported types are: uint8, int8, uint16, int16, uint32, int32, float32, float64.
	 * @param name - name of field.
	 * @param scale - result will be multiplicated by this value.
	 * @param precision - precision for getValueAsString method.
	 * @param bitnum - bit number
	 * @return Created Field object.
	 */
	public static Field create(int offset, String type, String name, double scale, int precision, int bitnum) {
		Field f;
		if (type.equals("uint8"))
			f = new Int8Field(UNSIGNED);
		else if (type.equals("int8"))
			f = new Int8Field(SIGNED);
		else if (type.equals("uint16"))
			f = new Int16Field(UNSIGNED);
		else if (type.equals("int16"))
			f = new Int16Field(SIGNED);		
		else if (type.equals("uint32"))
			f = new Int32Field(UNSIGNED);
		else if (type.equals("int32"))
			f = new Int32Field(SIGNED);		
		else if (type.equals("float32"))
			f = new Float32Field();
		else if (type.equals("float64"))
			f = new Float64Field();	
		else
			f = new Int8Field(UNSIGNED); 
		
		f.name = name;
		f.offset = offset;
		f.scale = scale;
		f.precision = precision;
		f.bitnum = bitnum;
		
		f.attr.put("bit", bitnum >= 0 ? "true" : "false");
		
		return f;
	}

	protected final boolean isBitField() {
		return bitnum >= 0;
	}
	
	protected final int getBit(long x) {
		if (bitnum < 0 || bitnum >= 32)
			return 0;
		else
			return (int)((x >> bitnum) & 1);
	}

	public String name;
	protected int offset;
	protected double scale;
	protected int precision;
	protected int bitnum;
	protected Map<String, String> attr = new HashMap<>();
}


class Int8Field extends Field {
	
	Int8Field(boolean signed) {
		this.signed = signed;
	}

	@Override
	public Double getValue(ByteBuffer buf) {
		try {
			byte x = buf.get(offset);
			if (isBitField())
				return (double)getBit(x);
			else
				return (double)(signed ? x : (x & 0xFF)) * scale;
		} catch (IndexOutOfBoundsException ex) {
			return new Double(0);
		}			
	}
	
	private boolean signed;
}


class Int16Field extends Field {

	Int16Field(boolean signed) {
		this.signed = signed;
	}

	@Override
	public Double getValue(ByteBuffer buf) {
		try {
			short x = buf.getShort(offset);
			if (isBitField())
				return (double)getBit(x);
			else			
				return (double)(signed ? x : (x & 0xFFFF)) * scale;
		} catch (IndexOutOfBoundsException ex) {
			return new Double(0);
		}			
	}
	
	private boolean signed;
}


class Int32Field extends Field {

	Int32Field(boolean signed) {
		this.signed = signed;
	}

	@Override
	public Double getValue(ByteBuffer buf) {
		try {
			long x = buf.getInt(offset);
			if (isBitField())
				return (double)getBit(x);
			else			
				return (double)(signed ? x : (x & 0xFFFFFFFF)) * scale;
		} catch (IndexOutOfBoundsException ex) {
			return new Double(0);
		}		
	}
	
	private boolean signed;
}


class Float32Field extends Field {
	
	@Override
	public Double getValue(ByteBuffer buf) {
		try {
			return (double)buf.getFloat(offset) * scale;
		} catch (IndexOutOfBoundsException ex) {
			return new Double(0);
		}		
	}
}


class Float64Field extends Field {
	
	@Override
	public Double getValue(ByteBuffer buf) {
		try {
			return buf.getDouble(offset) * scale;
		} catch (IndexOutOfBoundsException ex) {
			return new Double(0);
		}
	}
}
